package com.homework.loans;

import io.dropwizard.hibernate.UnitOfWork;
import org.joda.time.DateTime;
import org.joda.time.Interval;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static java.lang.String.format;
import static java.math.BigDecimal.ROUND_DOWN;
import static java.util.stream.Collectors.toList;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.ok;
import static javax.ws.rs.core.Response.status;
import static org.joda.time.DateTime.now;

/**
 * Resource for accessing loans.
 *
 * @author Blind
 */
@Path("/loans")
@Produces(MediaType.APPLICATION_JSON)
public class LoanResource {
    public static final int AM_06 = 6;
    public static final int MAX_APPLICATIONS = 3;

    private final LoanDao loanDao;
    private final CustomerDao customerDao;

    public LoanResource(LoanDao loanDao, CustomerDao customerDao) {
        this.loanDao = loanDao;
        this.customerDao = customerDao;
    }

    @GET
    @UnitOfWork
    @Path("/{customer}")
    public List<LoanAdapter> customerLoans(@PathParam("customer") int customerId) {
        return customerDao.loans(customerId).stream().map(LoanAdapter::new).collect(toList());
    }

    @PUT
    @UnitOfWork
    public Response tryLoan(Loan loan) {
        loan.startDate = now();

        Set<String> errors = validate(loan);

        return errors.isEmpty() ? ok(new LoanAdapter(loanDao.create(prepare(loan)))).build() :
                status(BAD_REQUEST).entity(errors).build();
    }

    @POST
    @UnitOfWork
    @Path("/{loan}")
    public Response extend(@PathParam("loan") int loanId) {
        Optional<Loan> loan = loanDao.find(loanId);
        loan.ifPresent(l -> {
            LoanExtension e = new LoanExtension();
            l.extensions.add(e);
            e.loan = l;
        });

        return loan.map(l -> ok(new LoanAdapter(loanDao.create(l)))).
                orElse(status(NOT_FOUND).entity("loan not found")).build();
    }

    private Loan prepare(Loan loan) {
        loan.customer = customerDao.createOrUpdate(loan.customer);
        loan.customer.loans.add(loan);
        return loan;
    }

    private Set<String> validate(Loan loan) {
        Set<String> errors = newHashSet();
        Interval interval = new Interval(now().withTimeAtStartOfDay(), now().withTimeAtStartOfDay().plusHours(AM_06));
        if (interval.contains(loan.startDate) &&
                loan.amount.setScale(2, ROUND_DOWN).equals(Loan.MAX_AMOUNT.setScale(2, ROUND_DOWN))) {
            errors.add("error.max.amount.night.time");
        }
        if (loanDao.countFromIp(loan.ip) > MAX_APPLICATIONS) {
            errors.add("max.application.from.ip.reached");
        }
        return errors;
    }

    /**
     * Adapter class for loan, not to get endless recursion with customer.
     */
    public static class LoanAdapter {
        public Integer id;
        public BigDecimal amount;
        public DateTime startDate;
        public Integer duration;
        public int customerId;
        public int interest;
        public List<String> extensions = newArrayList();

        public LoanAdapter(Loan loan) {
            this.id = loan.id;
            this.amount = loan.amount;
            this.startDate = loan.startDate;
            this.duration = loan.duration;
            this.customerId = loan.customer.id;
            this.interest = loan.interest();
            this.extensions = loan.extensions.stream().
                    map(e -> format("Loan extension. Submitted: %s", e)).collect(toList());
        }

        //For Jackson
        public LoanAdapter() {
        }
    }
}
